package FIT::Document::Parser::Sanitizer;

use FIT::Class;
use File::Basename;
use XML::Twig;
use IO::All -utf8;
use aliased 'HTML::Tidy';
use aliased 'FIT::Document::Parser::Text';
use aliased 'FIT::Document::Parser::Twig';

sub make_twig() {
    my ($class, %params) = @_;
    local $_;
    my ($html, $out_dir) = map { $params{$_} } qw(html out_dir);

    # Text parser requires a prepare for read
    $html = Text->prepare_for_read($html);

    # Tidy
    $html = tidy($html);

    # parse
    my $twig = Twig->make_twig;
    my $root = $twig->parse($html)->root;

    # remove col elements
    $_->delete for $root->descendants('col');

    # remove width and heights of cells and tables
    $_->del_att('width') for $root->descendants(qr/^(?:table|td)$/);

    # remove valign from rows and cells, handled by CSS
    $_->del_att('valign') for $root->descendants(qr/^(?:tr|td)$/);

    # remove all table attributes, except border- we use styles
    # border is kept so it looks like a table even with no CSS
    $_->del_att(qw( cellspacing cellpadding )) for $root->descendants('table');

    # remove BRs and stuff from empty cells
    for ($root->descendants('td'))
        { $_->cut_children if $_->children_trimmed_text eq '' }

    # remove P if it completely encloses all children of TD
    for ($root->descendants('td')) {
        my $first_child = $_->first_child;
        next unless $first_child;
        $first_child->erase if
            ($first_child eq $_->last_child) && ($first_child->tag eq 'p');
    }

    # add/fix linkgs to resources (css, js), copy the resources themselves
    my $head = $root->first_child('head');
    unless ($head) {
        $head = XML::Twig::Elt->new('head');
        $head->paste(first_child => $root);
    }

    my $css_link = $head->first_child('link');
    unless ($css_link) {
        $css_link = XML::Twig::Elt->new(link => {
            rel   => "StyleSheet",
            type  => "text/css",
            media => "screen, print",
        });
        $css_link->paste(first_child => $head);
    }
    $css_link->set_att(href => "fit.css");

    my $js_script = XML::Twig::Elt->new(script => {
        type => 'text/javascript',
        src  => "fit.js",
    });
    $js_script->paste(last_child => $head);

    copy_resources($out_dir);
    return $twig;
}

sub twig_as_HTML() { Text->prepare_for_write( pop->root->sprint ) }

sub copy_resources() {
    my $out_dir = io( shift );

    # resources are at Santizer.pm/../../../../../res/*
    my $res_dir = File::Spec->rel2abs(__FILE__);
    $res_dir = dirname($res_dir) for 1..5;
    $res_dir .= "/res";

    io($out_dir)->mkpath unless $out_dir->is_dir;
    for my $res_file (qw( fit.css fit.js )) {
        die "Can't copy resource [$res_file]: $!" unless
            (-f "$out_dir/$res_file") ||
            io("$out_dir/$res_file") < io("$res_dir/$res_file");
    }
}

sub tidy() {
    my $html = pop;
    my $tidy = Tidy->new({
        indent              => 'auto',
        output_xhtml        => 1,
        char_encoding       => 'utf8',
        accessibility_check => 0,
        wrap                => 0,
        vertical_space      => 0,
        bare                => 1,
        show_warnings       => 0,
    });
# print "# BEFORE [$html], AFTER [".$tidy->clean($html)."]\n";
    return $tidy->clean($html);
}


=head1 NAME

FIT::Document::Parser::Sanitizer - tidy, parse, and sanitize HTML


=head1 SYNOPSIS

    use aliased 'FIT::Document::Parser::Sanitizer';

    my $twig = Santizer->make_twig(
        out_dir => "results_dir",
        html    => q{
            <html>
              <table>
                <tr>
                  <td>foo</td>
                </tr>
              </table>
            </html>
        },
    );
    print $twig->root->first_table->fixture_name; # foo
    print Sanitizer->twig_as_HTML($twig);         # prints twig HTML


=head1 DESCRIPTION

Use C<make_twig()> to create a Twig. Pass the output dir for the result, and
the HTML as the only parameter.

The output dir is needed for handling the linked CSS file.

The HTML will be passed through C<HTML::Tidy>, parsed, then lightly sanitized.


=cut

